﻿/*
* Sunbox DataView
* Tool for customizing layout in SharePoint
*
* Examples and documentation at: http://sunbox.codeplex.com
* 
* Copyright (c) 2011 - 2012 SKB KONTUR
* Many people have submitted bugs, code, and offered their advice freely. Their support is greatly appreciated.
* 
* Version: 1.5.8 (06/06/2012)
* Requires: jQuery v1.3+ (for options), Fancybox 1.3.4 (for options), CodeMirror (for options)
*
* LGPL license:
*   http://www.gnu.org/licenses/lgpl.html
*/

var sunboxDataViewScriptIncluded = true;
//----------------------------------- Source State Machine -------------------------------
//Singleton Object for controlling data source loading
//If one dataview web part wants to load the same data source as the seconds
//she waits for loading this source by another web part
SunboxSourceStateMachine = (function () {

    function sunboxSourceStateMachine() {
        this.init();
    }

    sunboxSourceStateMachine.prototype.init = function () {
        this.sources = [];
        this.events = [];
    };

    sunboxSourceStateMachine.prototype.getSource = function (uid) {
        for (var i = 0; i < this.sources.length; i++) {
            if (this.sources[i].uid == uid)
                return this.sources[i];
        }
        return null;
    };

    //sunbox data view starts loading source
    sunboxSourceStateMachine.prototype.sourceLoadingStart = function (uid) {
        this.setSourceLoading(uid, true);
    };
    //sunbox data view ends loading source
    sunboxSourceStateMachine.prototype.sourceLoadingEnd = function (uid, items, options) {
        this.setSourceLoading(uid, false);
        //notify subscribers:
        var eventArgs = { items: items, options: options };
        this.notifyEvent("sourceLoaded", uid, eventArgs);
    };
    //notify on source loaded
    sunboxSourceStateMachine.prototype.onSourceLoaded = function (uid, func) {
        this.events.push({ name: "sourceLoaded", uid: uid, callback: func });
    };
    //is data source loading now?
    sunboxSourceStateMachine.prototype.isLoading = function (uid) {
        var source = this.getSource(uid);
        if (source == null) return false;
        return source.loading;
    };

    sunboxSourceStateMachine.prototype.setSourceLoading = function (uid, set) {
        var source = this.getSource(uid);
        if (source == null) {
            var newSource = { loading: set, uid: uid };
            this.sources.push(newSource);
        } else {
            source.loading = set;
        }
    };
    /* NOTIFICATION */

    sunboxSourceStateMachine.prototype.notifyEvent = function (eventName, uid, e) {
        for (var i = 0; i < this.events.length; i++) {
            if (this.events[i].name == eventName && this.events[i].uid == uid)
                this.events[i].callback(e);
        }
    };
    return sunboxSourceStateMachine;
})();
//---------------------------------------- Methods for XML serialization ---------------------------------
SunboxXmlSerializer = (function () {

    function sunboxXmlSerializer() {
        this.doc = this.createDocument("", "response", null);
    }
    sunboxXmlSerializer.prototype.serializeDataSourceToNode = function (datasource, items) {
        var root = this.doc.createElement(this.formatSourceTitle(datasource.Title));
        var rows = this.doc.createElement("Rows");
        root.appendChild(rows);
        //elm.appendChild(rows);
        if (items == null)
            return root;
        //serializing only required fields:
        var fields = this.getRequiredFieldsFromDataSource(datasource);

        for (var i = 0; i < items.length; i++) {
            var row = this.doc.createElement("Row");
            this.appendObjectToXml(items[i], fields, row);
            rows.appendChild(row);
        }
        return root;
    };

    sunboxXmlSerializer.prototype.getRequiredFieldsFromDataSource = function (datasource) {
        if (datasource.ViewFields.length == 0)
            return [];
        var result = [];
        var fields = this.convertStringToXml(datasource.ViewFields).getElementsByTagName("FieldRef");
        for (var i = 0; i < fields.length; i++) {
            var nameAttrValue = fields[i].getAttribute("Name");
            if (nameAttrValue == null) continue;
            result.push(nameAttrValue);
        }
        return result;
    };

    sunboxXmlSerializer.prototype.appendObjectToXml = function (obj, fields, elm) {
        var doc = elm.ownerDocument;
        for (var attr in obj) {
            if (attr.length == 0) continue;
            if (fields.length > 0 && this.inArray(attr, fields) < 0) continue;

            var value = obj[attr];
            //check for lookup
            var lookupValues = new SPFieldLookupCollection(value);
            var attrElm = doc.createElement(attr);
            if (attr.toLowerCase() == "fileref") { //custom processing
                if (lookupValues.length > 0) {
                    //fileref name:
                    var nameAttr = doc.createElement("Name");
                    nameAttr.appendChild(doc.createTextNode(lookupValues[0].LookupValue));
                    //fileref encoded name:
                    var encodedNameAttr = doc.createElement("EncodedName");
                    encodedNameAttr.appendChild(doc.createTextNode(this.urlEncode(lookupValues[0].LookupValue)));
                    //fileref suffix:
                    var suffix = this.getPathExtention(lookupValues[0].LookupValue);
                    var suffixAttr = doc.createElement("Suffix");
                    suffixAttr.appendChild(doc.createTextNode(suffix == undefined ? "" : suffix));

                    attrElm.appendChild(nameAttr);
                    attrElm.appendChild(encodedNameAttr);
                    attrElm.appendChild(suffixAttr);
                } else {
                    attrElm.appendChild(doc.createTextNode(value));
                }
            } else {
                if (lookupValues.length > 0) {
                    for (var j = 0; j < lookupValues.length; j++) {
                        var valueAttrElm = doc.createElement("Value");

                        var lookupIdElm = doc.createElement("LookupId");
                        lookupIdElm.appendChild(doc.createTextNode(lookupValues[j].LookupId));

                        var lookupValueElm = doc.createElement("LookupValue");
                        lookupValueElm.appendChild(doc.createTextNode(this.htmlEncode(lookupValues[j].LookupValue)));

                        valueAttrElm.appendChild(lookupIdElm);
                        valueAttrElm.appendChild(lookupValueElm);

                        attrElm.appendChild(valueAttrElm);
                    }
                } else {
                    attrElm.appendChild(doc.createTextNode(value));
                }
            }
            elm.appendChild(attrElm);
        }
        return elm;
    };
    sunboxXmlSerializer.prototype.inArray = function (elem, array, i) {
        var len;
        var indexOf = Array.prototype.indexOf;
        if (array) {
            if (indexOf) {
                return indexOf.call(array, elem, i);
            }
            len = array.length;
            i = i ? i < 0 ? Math.max(0, len + i) : i : 0;
            for (; i < len; i++) {
                if (i in array && array[i] === elem) {
                    return i;
                }
            }
        }
        return -1;
    };
    sunboxXmlSerializer.prototype.createDocument = function (ns, name, doctype) {
        var xmlDoc = null;
        if (window.ActiveXObject) {
            xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
            xmlDoc.async = false; //Enforce download of XML file first. IE only.
            xmlDoc.appendChild(xmlDoc.createElement(name));
        }
        else if (document.implementation && document.implementation.createDocument)
            xmlDoc = document.implementation.createDocument(ns, name, doctype);
        return xmlDoc;
    };
    sunboxXmlSerializer.prototype.convertStringToXml = function (text) {
        var doc;
        if (window.ActiveXObject) {
            doc = new ActiveXObject('Microsoft.XMLDOM');
            doc.loadXML(text);
        } else {
            var parser = new DOMParser();
            doc = parser.parseFromString(text, 'text/xml');
        }
        return doc;
    };
    sunboxXmlSerializer.prototype.parseHTML = function (html) {
        var root = document.createElement("div");
        root.innerHTML = html;
        return root;
    };
    sunboxXmlSerializer.prototype.convertXmlToString = function (oXml) {
        if (typeof oXml == "string") return oXml;
        if (window.ActiveXObject) {
            return oXml.xml;
        } else {
            return (new XMLSerializer()).serializeToString(oXml);
        }
    };
    //format data source from wrong characters
    sunboxXmlSerializer.prototype.formatSourceTitle = function (title) {
        var newTitle = title.toString().replace(" ", "_");
        return newTitle;
    };
    sunboxXmlSerializer.prototype.urlEncode = function (str) {
        return encodeURI(str);
    };
    sunboxXmlSerializer.prototype.htmlEncode = function (value) {
        var div = document.createElement('div');
        var text = document.createTextNode(value);
        div.appendChild(text);
        return div.innerHTML;
    };
    sunboxXmlSerializer.prototype.getPathExtention = function (filename) {
        return (/[.]/.exec(filename)) ? /[^.]+$/.exec(filename) : undefined;
    };
    return sunboxXmlSerializer;
})();
window.__sunboxXml = new SunboxXmlSerializer();
//------------------------------Sunbox SharePoint data manager-----------------------------
SunboxSpDataManager = (function () {

    function sunboxSpDataManager() {
        this.init();
    }

    sunboxSpDataManager.prototype.init = function () {

    };
    sunboxSpDataManager.prototype.sourcesStateMachine = new SunboxSourceStateMachine();
    //load datasource from SP:
    sunboxSpDataManager.prototype.loadDataSource = function (datasource, pagedData, uid, success, error) {
        var queryOptions = "<QueryOptions>";
        if (datasource.RecursiveScope)
            queryOptions += "<ViewAttributes Scope=\"Recursive\" />";
        if (pagedData.length > 0)
            queryOptions += "<Paging ListItemCollectionPositionNext=\"" + __sunboxXml.htmlEncode(pagedData) + "\" />";
        queryOptions += "</QueryOptions>";

        //check if data source is loading now:
        var $this = this;
        datasource.loading = true;
        if (this.sourcesStateMachine.isLoading(uid)) {
            //the same data source loading now, wait for loading:
            this.sourcesStateMachine.onSourceLoaded(uid, function (e) {
                datasource.loading = false;
                success(datasource, e.items, e.options);
            });
        } else {
            //starting ajax call:
            this.sourcesStateMachine.sourceLoadingStart(uid);
            //get sp list item from lists.asmx service:
            SPData.getSPListItems(datasource.ListId, datasource.ViewId, datasource.Query, datasource.ViewFields, datasource.RowLimit == 0 ? null : datasource.RowLimit, queryOptions, function (listItems, options) {
                datasource.loading = false;
                $this.sourcesStateMachine.sourceLoadingEnd(uid, listItems, options);
                success(datasource, listItems, options);
            }, function (ex) {
                datasource.loading = false;
                $this.sourcesStateMachine.sourceLoadingEnd(uid, null, null);
                error(ex);
            }, datasource.WebUrl);
        }
    };
    return sunboxSpDataManager;
})();
//-------------------------- Sunbox local storage data manager ----------------------------
SunboxLocalDataManager = (function () {
    function sunboxLocalDataManager() {
        this.init();
    }

    sunboxLocalDataManager.prototype.init = function () {
        this.cacheKeyIndentifier = "__sunboxdtv_";
        this.dateDiff = {
            inMinutes: function (d1, d2) {
                var t2 = d2.getTime();
                var t1 = d1.getTime();
                return parseInt((t2 - t1) / (60 * 1000));
            },
            inHours: function (d1, d2) {
                var t2 = d2.getTime();
                var t1 = d1.getTime();
                return parseInt((t2 - t1) / (3600 * 1000));
            },
            inDays: function (d1, d2) {
                var t2 = d2.getTime();
                var t1 = d1.getTime();
                return parseInt((t2 - t1) / (24 * 3600 * 1000));
            },
            inWeeks: function (d1, d2) {
                var t2 = d2.getTime();
                var t1 = d1.getTime();
                return parseInt((t2 - t1) / (24 * 3600 * 1000 * 7));
            }
        };
    };
    //load datasource from local storage:
    sunboxLocalDataManager.prototype.loadDataSource = function (datasource, pagedData, luid) {
        var cache = this.getCacheObject();
        if (cache == null || datasource.Modified == null)
            return null; //not cacheable data source

        var pgd = typeof pagedData == "undefined" || pagedData == null || pagedData.length == 0 ? "" : MD5(pagedData);

        var modified = cache.getItem(this.cacheKeyIndentifier + luid + "_" + pgd + "_modified");
        if (modified == null) return null;

        if (modified != datasource.Modified) {//data source in cache expired
            this.removeDataSource(luid); //remove previous version of data source
            return null; //data source not exists in cache
        }

        var ldsSource = {};
        ldsSource.data = cache.getItem(this.cacheKeyIndentifier + luid + "_" + pgd + "_data"); ;
        ldsSource.pagedData = cache.getItem(this.cacheKeyIndentifier + luid + "_" + pgd + "_pageddata");
        //update last used date of data source:
        cache.setItem(this.cacheKeyIndentifier + luid + "_lastused", (new Date()).toString());
        return ldsSource;
    };
    //save data source to cache
    //datasource - data source object
    //pagedData - pagedData for quering this data source
    //luid - local uid of the data source
    sunboxLocalDataManager.prototype.saveDataSource = function (datasource, pagedData, luid) {
        var cache = this.getCacheObject();
        if (cache == null || datasource.Modified == null)
            return; //Modified date of the data source is unkhnown, web can't to cache it
        //create data as string:
        var pgd = typeof pagedData == "undefined" || pagedData == null || pagedData.length == 0 ? "" : MD5(pagedData);
        var data = __sunboxXml.convertXmlToString(datasource.rootElement);
        try {
            //saving data:
            cache.setItem(this.cacheKeyIndentifier + luid + "_" + pgd + "_data", data);
            //saving modified:
            if (datasource.pagedData != null && datasource.pagedData.length > 0)
                cache.setItem(this.cacheKeyIndentifier + luid + "_" + pgd + "_pageddata", datasource.pagedData);
            //saving modified:
            cache.setItem(this.cacheKeyIndentifier + luid + "_" + pgd + "_modified", datasource.Modified);
            //set last user time stamp:
            cache.setItem(this.cacheKeyIndentifier + luid + "_lastused", (new Date()).toString());
        }
        catch (e) {
            if (e == window.QUOTA_EXCEEDED_ERR) {//local cache limit exceeded
                this.clearCache();
            }
        }
    };

    sunboxLocalDataManager.prototype.getCacheObject = function () {
        if (typeof (localStorage) == "undefined") {
            return null;
        }
        else {
            return localStorage;
        }
    };

    sunboxLocalDataManager.prototype.isSupportedStorage = function () {
        return this.getCacheObject() != null;
    };
    sunboxLocalDataManager.prototype.removeDataSource = function (luid) {
        var cache = this.getCacheObject();
        if (cache == null) return;
        var dsKey = this.cacheKeyIndentifier + luid;
        for (var key in cache) {
            if (key.startsWith(dsKey)) {
                cache.removeItem(key);
            }
        }
    };
    //returnd luids of all saved data sources:
    sunboxLocalDataManager.prototype.getAllDataSourceslUids = function () {
        var cache = this.getCacheObject();
        if (cache == null) return [];
        var allLuids = [];
        for (var key in cache) {
            if (key.startsWith(this.cacheKeyIndentifier) && key.endsWith("_lastused")) {
                allLuids.push(key.replace(this.cacheKeyIndentifier, "").replace("_lastused", ""));
            }
        }
        return allLuids;
    };
    //function to clear expired data sources in cache
    sunboxLocalDataManager.prototype.recycleOldDataSources = function () {
        if (!this.isSupportedStorage())
            return; //browsert not support
        var cache = this.getCacheObject();
        //get all data source from cache:
        var ids = this.getAllDataSourceslUids();
        for (var i = 0; i < ids.length; i++) {
            var sLastUsed = cache.getItem(this.cacheKeyIndentifier + ids[i] + "_lastused");
            if (sLastUsed == null) continue;
            //retrive last used date:
            var dLastUsed = new Date(sLastUsed);
            if (this.dateDiff.inHours(dLastUsed, new Date()) > 48) {//48 hours to expired
                //data sources expired:
                this.removeDataSource(ids[i]);
            }
        }
    };

    sunboxLocalDataManager.prototype.clearCache = function () {
        var cache = this.getCacheObject();
        if (cache == null) return;
        cache.clear();
    };
    return sunboxLocalDataManager;
})();
//-------------------------------------- Common sunbox data manager -----------------------------
SunboxCommonDataManager = (function () {
    function sunboxCommonDataManager() {
        this.init();
    }
    //data manager to retrieve data from SP:
    sunboxCommonDataManager.prototype.sunboxSpDataManager = new SunboxSpDataManager();
    //data manager to retrieve data from local cache:
    sunboxCommonDataManager.prototype.sunboxLocalDataManager = new SunboxLocalDataManager();
    //utils method for xml zerialization:

    sunboxCommonDataManager.prototype.init = function () {
        //recycle old data sources
        if (typeof (window.__sunboxdtvrecylecacheinit) == "undefined") {
            window.__sunboxdtvrecylecacheinit = true;
            var $this = this;
            setTimeout(function () {
                $this.sunboxLocalDataManager.recycleOldDataSources.call($this.sunboxLocalDataManager);
            }, 2000);
        }
    };
    //load datasource:
    sunboxCommonDataManager.prototype.loadDataSource = function (datasource, pagedData, success, error) {
        //uid - for quering data from SP
        //luid - for quering data from local cache, luid does not contain info about paging
        var uid, luid;
        var $this = this;
        //first: check in cache
        if (this.sunboxLocalDataManager.isSupportedStorage()) { //browser supports localStorage, get items from them
            if (datasource.CacheOnClient) {
                //get local uid for current data source to check in cache:
                luid = this.calculateDataSourceUid(datasource, "");
                var ldsSource = this.sunboxLocalDataManager.loadDataSource(datasource, pagedData, luid);
                if (ldsSource != null) {//get data from cache
                    datasource.pagedData = ldsSource.pagedData;
                    datasource.rootElement = __sunboxXml.convertStringToXml(ldsSource.data).firstChild;
                    success(datasource);
                    return;
                }
                //cache not found:
            }
            //load source from SP:
            uid = this.calculateDataSourceUid(datasource, pagedData);
            this.loadDataSourceFromSp(datasource, pagedData, uid, function (ds) {
                // save to cache if allowed and supported:
                if (ds.CacheOnClient) {
                    $this.sunboxLocalDataManager.saveDataSource(ds, pagedData, luid);
                }
                //notify data view:
                success(ds);
            }, error);
        }
        else {//local storage not support, gets data from SP.
            uid = this.calculateDataSourceUid(datasource, pagedData);
            this.loadDataSourceFromSp(datasource, pagedData, uid, function (ds) {
                // save to cache if allowed and supported:
                if (ds.CacheOnClient && $this.sunboxLocalDataManager.isSupportedStorage()) {
                    luid = this.calculateDataSourceUid(ds, "");
                    $this.sunboxLocalDataManager.saveDataSource(datasource, pagedData, luid);
                }
                //notify data view:
                success(ds);
            }, error);
        }
    };
    sunboxCommonDataManager.prototype.loadDataSourceFromSp = function (datasource, pagedData, uid, success, error) {
        this.sunboxSpDataManager.loadDataSource(datasource, pagedData, uid,
            function (ds, listItems, options) {//successfully loaded from SP
                if (options == null)
                    options = {};
                ds.pagedData = options.ListItemCollectionPositionNext;
                ds.rootElement = __sunboxXml.serializeDataSourceToNode(ds, listItems);
                success(ds);
            },
            function (ex) {//error loading data from SP:
                datasource.items = null;
                datasource.rootElement = __sunboxXml.serializeDataSourceToNode(datasource, null);
                error(ex);
            });
    };
    sunboxCommonDataManager.prototype.calculateDataSourceUid = function (datasource, pagedData) {
        var queryOptions = "<QueryOptions>";
        if (datasource.RecursiveScope)
            queryOptions += "<ViewAttributes Scope=\"Recursive\" />";
        if (pagedData.length > 0)
            queryOptions += "<Paging ListItemCollectionPositionNext=\"" + __sunboxXml.htmlEncode(pagedData) + "\" />";
        queryOptions += "</QueryOptions>";
        return this.getDataSourceUid(datasource, queryOptions);
    };
    //get source uid
    sunboxCommonDataManager.prototype.getDataSourceUid = function (datasource, queryOptions) {
        //get current user id:
        var userId = "";
        if (typeof (window._spPageContextInfo) != 'undefined') {
            userId = window._spPageContextInfo.userId.toString();
        }
        var sToHash = userId + datasource.ListId + datasource.ViewId + datasource.Query + datasource.ViewFields + +datasource.RowLimit + queryOptions;
        return MD5(sToHash);
    };
    return sunboxCommonDataManager;
})();


//--------------------------------------- SUNBOX DATAVIEW -------------------------------
SunboxDataView = (function () {

    function sunboxDataView(container, options) {
        this.options = options;
        if (typeof this.options.parameters == "undefined")
            this.options.parameters = [];
        this.container = container;
        this.events = [];
        this.errors = [];
        this.windowContainer = document.getElementById('s4-workspace');
        this.scrollingBeforePixels = 150;
        this.init();
    }
    sunboxDataView.prototype.dataManager = new SunboxCommonDataManager();
    //utils method for xml zerialization:
    sunboxDataView.prototype.init = function () {
        if (typeof this.options.initFunction == 'function') {
            //call init function
            this.options.initFunction.call(this);
        }

        //init lang
        if (typeof sunboxDataView.lang[this.options.lang] != 'undefined')
            this.locale = sunboxDataView.lang[this.options.lang];
        else
            this.locale = sunboxDataView.lang.en;

        this.showLoader(); //show loader if needed
        this.initQueryStrings(); //read query strings

        if (typeof this.windowContainer != "undefined" && this.getSourcesByPagerStyle(3).length > 0) {
            var $this = this;
            this.windowContainer.onscroll = function () {
                $this.checkPagerLoadOnScroll.call($this);
            };
        }
        if (this.options.datasources.length == 0) {
            this.container.appendChild(document.createTextNode(this.locale.dataViewNoDataSourceMessage));
        } else {
            this.loadDataSources(this.options.datasources, function () {
                this.hideLoader();
                this.renderDataSources(this.options.datasources);
                this.drawPagers();
                this.notifyAfterRender();
                this.checkPagerLoadOnScroll();
            });
        }
    };

    sunboxDataView.prototype.tabClicked = function (e) {
        var target = this.getTarget(e);
        if (typeof target == "undefined")
            return;
        var nodeToShow = target.nextSibling;
        while (nodeToShow != undefined && nodeToShow.nodeType == 3) {
            nodeToShow = nodeToShow.nextSibling;
        }
        var current = nodeToShow.style.display;
        if (current == "block")
            nodeToShow.style.display = "none";
        else
            nodeToShow.style.display = "block";
    };

    sunboxDataView.prototype.htmlDecode = function (value) {
        var div = document.createElement('div');
        div.innerHTML = value;
        return div.firstChild.nodeValue;
    };
    //rendering xml with current xsl template
    sunboxDataView.prototype.renderXml = function (xmldoc) {
        var xsl = __sunboxXml.convertStringToXml(this.htmlDecode(this.options.xsltemplate.Xsl));

        if (this.options.debug) {// debug mode enabled
            var parseErrrorNode = xsl.getElementsByTagName("parsererror");
            if (parseErrrorNode != null && parseErrrorNode.length > 0) {
                this.registerError("", this.getHtmlOfXmlElement(parseErrrorNode[0]), "Error while parsing xsl template");
            }

            var $this = this;
            var tabclicked = function (e) {
                $this.tabClicked.call($this, e);
            };
            //XML CONTAINER
            var xmldiv = document.createElement("div");
            var xmllink = document.createElement("a");
            xmllink.appendChild(document.createTextNode("Source XML"));
            xmllink.setAttribute("href", "javascript:void(0);");
            xmllink.onclick = tabclicked;

            var xmlcontent = document.createElement("div");
            xmlcontent.appendChild(document.createTextNode(__sunboxXml.convertXmlToString(xmldoc)));
            xmlcontent.style.display = "none";

            xmldiv.appendChild(xmllink);
            xmldiv.appendChild(xmlcontent);
            this.container.appendChild(xmldiv);

            //XSL CONTAINER
            var xsldiv = document.createElement("div");
            var xsllink = document.createElement("a");
            xsllink.appendChild(document.createTextNode("Source XSL"));
            xsllink.setAttribute("href", "javascript:void(0);");
            xsllink.onclick = tabclicked;

            var xslcontent = document.createElement("div");
            xslcontent.appendChild(document.createTextNode(__sunboxXml.convertXmlToString(xsl)));
            xslcontent.style.display = "none";

            xsldiv.appendChild(xsllink);
            xsldiv.appendChild(xslcontent);
            this.container.appendChild(xsldiv);

            //ERROR CONTAINER
            if (this.errors.length > 0) {
                var errorsdiv = document.createElement("div");
                var errorslink = document.createElement("a");
                errorslink.appendChild(document.createTextNode("Errors"));
                errorslink.setAttribute("href", "javascript:void(0);");
                errorslink.onclick = tabclicked;

                var errorscontent = document.createElement("div");
                for (var i = 0; i < this.errors.length; i++) {
                    var p = document.createElement("p");
                    p.style.color = "red";
                    p.appendChild(document.createTextNode(this.errors[i].errorTitle + ", " + this.errors[i].errorCode + ": "));
                    var nodesToAppend = __sunboxXml.parseHTML(this.errors[i].errorMessage).childNodes;
                    for (var j = nodesToAppend.length; j > 0; j--) {
                        p.appendChild(nodesToAppend[0]);
                    }
                    errorscontent.appendChild(p);
                }
                errorscontent.style.display = "none";

                errorsdiv.appendChild(errorslink);
                errorsdiv.appendChild(document.createTextNode("(" + this.errors.length + ")"));
                errorsdiv.appendChild(errorscontent);
                this.container.appendChild(errorsdiv);
            }
        }
        return this.xslTransform(xmldoc, xsl);
    };
    //Append or display datasources
    sunboxDataView.prototype.renderDataSources = function (datasources) {
        //serializing to xml:
        var doc = this.serializeDataSourcesToXml(datasources);
        //rendering xml:
        this.wraper = document.createElement("div");
        this.wraper.className = "sunbox-dataview-wrap";
        var html = this.renderXml(doc);
        if (html == null) return; //error
        this.notifyBeforeRender(this.container, html);
        if (typeof html == "string") //IE
        {
            this.wraper.innerHTML = html;
        }
        else {
            this.wraper.appendChild(html);
        }
        this.container.appendChild(this.wraper);
    };

    //Transform xml
    sunboxDataView.prototype.xslTransform = function (xml, xsl) {
        // code for IE
        if (window.ActiveXObject) {
            return xml.transformNode(xsl);
        }
        // code for Mozilla, Firefox, Opera, etc.
        else if (document.implementation && document.implementation.createDocument) {
            var xsltProcessor = new XSLTProcessor();
            xsltProcessor.importStylesheet(xsl);
            var resultDocument = xsltProcessor.transformToFragment(xml, document);
            return resultDocument;
        }
        return "XSL transformation failed: browser not supported.";
    };

    // Request all data sources
    sunboxDataView.prototype.loadDataSources = function (datasources, callback) {
        var sourceCount = datasources.length;
        var loadedSources = 0;

        for (var i = 0; i < datasources.length; i++) {
            if (datasources[i].Title.length == 0) continue;
            this.preprocessDataSource(datasources[i]);
            this.notifySourceLoadStart(datasources[i]);
            this.loadDataSource(datasources[i], "", function (datasource) {
                this.notifySourceLoadEnd(datasource);
                loadedSources++;
                if (loadedSources == sourceCount)
                    callback.call(this);
            });
        }
    };

    //Load single data source
    sunboxDataView.prototype.loadDataSource = function (datasource, pagedData, callback) {
        if (datasource.loading) return;
        var $this = this;
        this.dataManager.loadDataSource(datasource, pagedData,
            function (ds) { //data source successfully loaded
                //node: xml node of data source
                callback.call($this, ds);
            },
            function (ex) {//error loading data source:
                $this.registerError(ex.errorCode, ex.errorMessage, "Error loading data from '" + datasource.Title + "'");
                callback.call($this, datasource);
            }
        );
    };

    sunboxDataView.prototype.registerError = function (errorCode, errorMessage, errorTitle) {
        if (typeof (this.errors) == "undefined")
            this.errors = [];
        this.errors.push({ errorCode: errorCode, errorMessage: errorMessage, errorTitle: errorTitle });
    };

    sunboxDataView.prototype.preprocessDataSource = function (datasource) {
        var regex = /\{(\w+)\[(.+?)\]\}/g;
        var m = regex.exec(datasource.Query);
        while (m != null) {
            var sourceName = m[1];
            var parameterName = m[2];
            if (sourceName.toString().toLowerCase() == "querystring") { //query string replacement
                datasource.Query = datasource.Query.replace(m[0], typeof (this.querySt[parameterName]) == "undefined" ? "" : this.querySt[parameterName]);
            } else if (sourceName.toString().toLowerCase() == "params") { //custom parameter replacement
                var pvalue = this.getParameterValue(parameterName);
                datasource.Query = datasource.Query.replace(m[0], pvalue == null ? "" : pvalue);
            }
            m = regex.exec(datasource.Query);
        }
    };

    sunboxDataView.prototype.serializeDataSourcesToXml = function (datasources) {
        var doc = __sunboxXml.createDocument("", "response", null);
        var root = doc.childNodes[0];
        //Bindings:
        this.serializeBindings(root);
        //Data Sources:
        for (var i = 0; i < datasources.length; i++) {
            if (datasources[i].Title.length == 0) continue;
            // var sourceElm = doc.createElement(this.formatSourceTitle(datasources[i].Title));
            root.appendChild(datasources[i].rootElement);
        }
        return doc;
    };

    sunboxDataView.prototype.getDataSourceById = function (id) {
        var datasources = this.options.datasources;
        for (var i = 0; i < datasources.length; i++) {
            if (datasources[i].Id == id)
                return datasources[i];
        }
        return null;
    };

    sunboxDataView.prototype.serializeBindings = function (elm) {
        var doc = elm.ownerDocument;
        var bindingElm = doc.createElement("__bindings");
        var queryStringsElm = doc.createElement("QueryStrings");
        bindingElm.appendChild(queryStringsElm);
        __sunboxXml.appendObjectToXml(this.querySt, [], queryStringsElm);

        var paramsElm = doc.createElement("Parameters");


        for (var i = 0; i < this.options.parameters.length; i++) {
            var param = this.options.parameters[i];
            var paramElm = doc.createElement(param.name);
            paramElm.appendChild(doc.createTextNode(param.value));
            paramsElm.appendChild(paramElm);
        }

        bindingElm.appendChild(paramsElm);

        elm.appendChild(bindingElm);
        return elm;
    };

    sunboxDataView.prototype.showLoader = function () {
        if (!this.options.showloader || this.options.loaderimage.length == 0) return;
        this.loaderImage = document.createElement("img");
        this.loaderImage.setAttribute("src", this.options.loaderimage);
        this.loaderImage.setAttribute("alt", "loading...");
        this.loaderImage.className = "sunboxdataview-loader";
        this.container.appendChild(this.loaderImage);
    };
    sunboxDataView.prototype.hideLoader = function () {
        if (typeof this.loaderImage == "undefined") return;
        this.container.removeChild(this.loaderImage);
    };

    sunboxDataView.prototype.addParameter = function (name, value) {
        this.options.parameters.push({ name: name, value: value });
    };
    sunboxDataView.prototype.getParameterValue = function (name) {
        for (var i = 0; i < this.options.parameters.length; i++) {
            var param = this.options.parameters[i];
            if (param.name.toLowerCase() == name.toLowerCase()) {
                return param.value;
            }
        }
        return null;
    };
    sunboxDataView.prototype.queryString = function (ji) {
        return this.queryStrings[ji];
    };
    sunboxDataView.prototype.initQueryStrings = function () {
        if (typeof (this.querySt) != 'undefined')
            return this.querySt;

        this.querySt = {};
        var hu = window.location.search.substring(1);
        var gy = hu.split("&");
        for (var i = 0; i < gy.length; i++) {
            var ft = gy[i].split("=");
            this.querySt[ft[0].toLowerCase()] = ft[1];
        }
        return this.querySt;
    };

    /*================== PAGER ==================== */
    sunboxDataView.prototype.drawPagers = function () {
        var datasources = this.options.datasources;
        for (var i = 0; i < datasources.length; i++) {
            if (datasources[i].PagerStyle > 0 && datasources[i].PagerStyle < 3 && typeof (datasources[i].pagedData) != 'undefined' && datasources[i].pagedData != null) {
                //get pager container:
                var pagerContainer;
                if (datasources[i].PagerContainerId.length > 0) {
                    pagerContainer = document.getElementById(datasources[i].PagerContainerId);
                    if (pagerContainer == null)
                        pagerContainer = this.container;
                } else {
                    pagerContainer = this.container;
                }
                //append pager html
                var pagerWrap = this.getDataSourcePagerHtml(datasources[i]);
                //remember pager wrapper for data source:
                datasources[i].pagerWrap = pagerWrap;
                pagerContainer.appendChild(pagerWrap);
            }
        }
    };
    sunboxDataView.prototype.getDataSourcePagerHtml = function (datasource) {
        var pagerWrap = document.createElement("div");
        pagerWrap.setAttribute("data-id", datasource.Id);
        pagerWrap.className = "snbxdv-pager";

        var pagerLink = document.createElement("a");
        pagerLink.setAttribute("href", "javascript:void(0);");
        var $this = this;
        pagerLink.onclick = function () {
            var dataSourceId = datasource.Id;
            var dsource = $this.getDataSourceById(dataSourceId);
            $this.loadPagedData.call($this, dsource);
        };
        pagerLink.className = "snbxdv-plink";
        pagerLink.appendChild(document.createTextNode(datasource.PagerDisplayName.length == 0 ? "More..." : datasource.PagerDisplayName));
        pagerWrap.appendChild(pagerLink);
        return pagerWrap;
    };

    sunboxDataView.prototype.loadPagedData = function (dsource) {
        if (dsource.Title.length == 0) return;
        var pagedData = typeof (dsource.pagedData) != 'undefined' && dsource.pagedData != null ? dsource.pagedData : "";
        if (pagedData.length == 0) return; //no paging

        this.loadDataSource(dsource, pagedData, function (datasource) {
            //serializing to xml:
            var doc = this.serializeDataSourcesToXml(this.options.datasources);
            //rendering xml:
            var html = this.renderXml(doc);
            var contentContainer = this.wraper;
            var htmltoappend;
            if (datasource.DynamicContentContainerId.length == 0) {
                htmltoappend = html;
            } else {
                var container = document.getElementById(datasource.DynamicContentContainerId);
                var sourceContainer;

                if (typeof html == "string") {//IE
                    html = __sunboxXml.parseHTML(html);
                }

                sourceContainer = this.findElementByIdInDocument(html, datasource.DynamicContentContainerId);
                if (container != null)
                    contentContainer = container;
                htmltoappend = sourceContainer != null ? this.getHtmlOfXmlElement(sourceContainer) : html;
            }
            //refresh html
            switch (datasource.PagerStyle) {
                case 1:
                    contentContainer.innerHTML = htmltoappend;
                    break;
                case 2:
                case 3:
                    var nodesToAppend = __sunboxXml.parseHTML(htmltoappend).childNodes;
                    for (var i = nodesToAppend.length; i > 0; i--) {
                        contentContainer.appendChild(nodesToAppend[0]);
                    }
                    //contentContainer.innerHTML += htmltoappend;
                    break;
            }
            //hide pager if no pages:
            if (typeof (dsource.pagedData) == 'undefined' || dsource.pagedData == null) {
                //the last page:
                if (typeof dsource.pagerWrap != "undefined")
                    dsource.pagerWrap.style.display = "none";
            }
            this.notifyAfterRender();
            //we need to relaculate s4-workspace dimentions:
            if (typeof (window.FixRibbonAndWorkspaceDimensions) != "undefined")
                window.FixRibbonAndWorkspaceDimensions();
            var $this = this;
            setTimeout(function () {
                $this.checkPagerLoadOnScroll.call($this);
            }, 10); //timeout need for IE

        });
    };
    sunboxDataView.prototype.findElementByIdInDocument = function (element, id) {
        if (element == null) return null;
        var idvalue;
        if (element.attributes == null)
            idvalue = null;
        else
            idvalue = element.getAttribute("id");

        if (idvalue == null || id != idvalue) {
            for (var i = 0; i < element.childNodes.length; i++) {
                var elm = this.findElementByIdInDocument(element.childNodes[i], id);
                if (elm != null)
                    return elm;
            }
            return null;
        }
        else {
            return element;
        }
    };
    sunboxDataView.prototype.getHtmlOfXmlElement = function (element) {
        if (typeof element.innerHTML != "undefined")
            return element.innerHTML;
        //IE:
        var html = "";
        for (var i = 0; i < element.childNodes.length; i++) {
            html += element.childNodes[i].xml;
        }
        return html;
    };
    sunboxDataView.prototype.getSourcesByPagerStyle = function (pageStyle) {
        var filteredSources = [];
        for (var i = 0; i < this.options.datasources.length; i++) {
            if (this.options.datasources[i].PagerStyle == pageStyle)//autoload
            {
                filteredSources.push(this.options.datasources[i]);
            }
        }
        return filteredSources;
    };

    sunboxDataView.prototype.checkPagerLoadOnScroll = function () {
        var iScrollableHeight = this.windowContainer.scrollHeight - this.scrollingBeforePixels;
        var iVisibleHeight = this.windowContainer.clientHeight;
        var iScrollTop = this.windowContainer.scrollTop;

        if ((iScrollTop + iVisibleHeight) >= iScrollableHeight) {
            //get sources:
            var autoLoadedSources = this.getSourcesByPagerStyle(3);
            for (var i = 0; i < autoLoadedSources.length; i++) {
                this.loadPagedData(autoLoadedSources[i]);
            }
        }
    };

    sunboxDataView.prototype.getTarget = function (e) {
        var target = null;
        if (!e) e = window.event;
        if (e.target) target = e.target;
        else if (e.srcElement) target = e.srcElement;
        if (target.nodeType == 3) // defeat Safari bug
            target = target.parentNode;
        return target;
    };
    /*================== EVENTS ==================== */
    sunboxDataView.prototype.afterRender = function (func) {
        this.events.push({ name: "afterRender", callback: func });
    };
    sunboxDataView.prototype.notifyAfterRender = function () {
        this.notifyEvent("afterRender");
    };
    sunboxDataView.prototype.beforeRender = function (func) {
        this.events.push({ name: "beforeRender", callback: func });
    };
    sunboxDataView.prototype.notifyBeforeRender = function (container, html) {
        var e = { container: container, html: html };
        this.notifyEvent("beforeRender", e);
    };

    sunboxDataView.prototype.onSourceLoadStart = function (func) {
        this.events.push({ name: "sourceLoadStart", callback: func });
    };
    sunboxDataView.prototype.notifySourceLoadStart = function (datasource) {
        this.notifyEvent("sourceLoadStart", datasource);
    };

    sunboxDataView.prototype.onSourceLoadEnd = function (func) {
        this.events.push({ name: "sourceLoadEnd", callback: func });
    };
    sunboxDataView.prototype.notifySourceLoadEnd = function (datasource) {
        this.notifyEvent("sourceLoadEnd", datasource);
    };

    sunboxDataView.prototype.notifyEvent = function (eventName, e) {
        for (var i = 0; i < this.events.length; i++) {
            if (this.events[i].name == eventName)
                if (!this.events[i].callback(e)) break;
        }
    };


    return sunboxDataView;
})();

//MD5 HASH function
var MD5 = function (string) {

    function RotateLeft(lValue, iShiftBits) {
        return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
    }

    function AddUnsigned(lX, lY) {
        var lX4, lY4, lX8, lY8, lResult;
        lX8 = (lX & 0x80000000);
        lY8 = (lY & 0x80000000);
        lX4 = (lX & 0x40000000);
        lY4 = (lY & 0x40000000);
        lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
        if (lX4 & lY4) {
            return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
        }
        if (lX4 | lY4) {
            if (lResult & 0x40000000) {
                return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
            } else {
                return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
            }
        } else {
            return (lResult ^ lX8 ^ lY8);
        }
    }

    function F(x, y, z) {
        return (x & y) | ((~x) & z);
    }

    function G(x, y, z) {
        return (x & z) | (y & (~z));
    }

    function H(x, y, z) {
        return (x ^ y ^ z);
    }

    function I(x, y, z) {
        return (y ^ (x | (~z)));
    }

    function FF(a, b, c, d, x, s, ac) {
        a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));
        return AddUnsigned(RotateLeft(a, s), b);
    }

    ;

    function GG(a, b, c, d, x, s, ac) {
        a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));
        return AddUnsigned(RotateLeft(a, s), b);
    }

    ;

    function HH(a, b, c, d, x, s, ac) {
        a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));
        return AddUnsigned(RotateLeft(a, s), b);
    }

    ;

    function II(a, b, c, d, x, s, ac) {
        a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));
        return AddUnsigned(RotateLeft(a, s), b);
    }

    ;

    function ConvertToWordArray(string) {
        var lWordCount;
        var lMessageLength = string.length;
        var lNumberOfWords_temp1 = lMessageLength + 8;
        var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
        var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
        var lWordArray = Array(lNumberOfWords - 1);
        var lBytePosition = 0;
        var lByteCount = 0;
        while (lByteCount < lMessageLength) {
            lWordCount = (lByteCount - (lByteCount % 4)) / 4;
            lBytePosition = (lByteCount % 4) * 8;
            lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition));
            lByteCount++;
        }
        lWordCount = (lByteCount - (lByteCount % 4)) / 4;
        lBytePosition = (lByteCount % 4) * 8;
        lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
        lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
        lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
        return lWordArray;
    }

    ;

    function WordToHex(lValue) {
        var WordToHexValue = "", WordToHexValue_temp = "", lByte, lCount;
        for (lCount = 0; lCount <= 3; lCount++) {
            lByte = (lValue >>> (lCount * 8)) & 255;
            WordToHexValue_temp = "0" + lByte.toString(16);
            WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length - 2, 2);
        }
        return WordToHexValue;
    }

    ;

    function Utf8Encode(string) {
        string = string.replace(/\r\n/g, "\n");
        var utftext = "";
        for (var n = 0; n < string.length; n++) {
            var c = string.charCodeAt(n);
            if (c < 128) {
                utftext += String.fromCharCode(c);
            } else if ((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            } else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }
        }
        return utftext;
    }

    ;
    var x = Array();
    var k, AA, BB, CC, DD, a, b, c, d;
    var S11 = 7, S12 = 12, S13 = 17, S14 = 22;
    var S21 = 5, S22 = 9, S23 = 14, S24 = 20;
    var S31 = 4, S32 = 11, S33 = 16, S34 = 23;
    var S41 = 6, S42 = 10, S43 = 15, S44 = 21;
    string = Utf8Encode(string);
    x = ConvertToWordArray(string);
    a = 0x67452301;
    b = 0xEFCDAB89;
    c = 0x98BADCFE;
    d = 0x10325476;
    for (k = 0; k < x.length; k += 16) {
        AA = a;
        BB = b;
        CC = c;
        DD = d;
        a = FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
        d = FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
        c = FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
        b = FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
        a = FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
        d = FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
        c = FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
        b = FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
        a = FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
        d = FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
        c = FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
        b = FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
        a = FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
        d = FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
        c = FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
        b = FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
        a = GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
        d = GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
        c = GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
        b = GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
        a = GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
        d = GG(d, a, b, c, x[k + 10], S22, 0x2441453);
        c = GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
        b = GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
        a = GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
        d = GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
        c = GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
        b = GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
        a = GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
        d = GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
        c = GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
        b = GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
        a = HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
        d = HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
        c = HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
        b = HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
        a = HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
        d = HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
        c = HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
        b = HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
        a = HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
        d = HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
        c = HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
        b = HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
        a = HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
        d = HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
        c = HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
        b = HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
        a = II(a, b, c, d, x[k + 0], S41, 0xF4292244);
        d = II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
        c = II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
        b = II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
        a = II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
        d = II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
        c = II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
        b = II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
        a = II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
        d = II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
        c = II(c, d, a, b, x[k + 6], S43, 0xA3014314);
        b = II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
        a = II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
        d = II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
        c = II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
        b = II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
        a = AddUnsigned(a, AA);
        b = AddUnsigned(b, BB);
        c = AddUnsigned(c, CC);
        d = AddUnsigned(d, DD);
    }
    var temp = WordToHex(a) + WordToHex(b) + WordToHex(c) + WordToHex(d);
    return temp.toLowerCase();
};